#include "hud.h"
#include "cl_util.h"
#include "camera.h"
#include "kbutton.h"
#include "cvardef.h"
#include "usercmd.h"
#include "const.h"
#include "camera.h"
#include "in_defs.h"
#include "windows.h"

float CL_KeyState(kbutton_t *key);

extern cl_enginefunc_t gEngfuncs;

#define CAM_DIST_DELTA 1.0
#define CAM_ANGLE_DELTA 2.5
#define CAM_ANGLE_SPEED 2.5
#define CAM_MIN_DIST 30.0
#define CAM_ANGLE_MOVE .5
#define MAX_ANGLE_DIFF 10.0
#define PITCH_MAX 90.0
#define PITCH_MIN 0
#define YAW_MAX 135.0
#define YAW_MIN -135.0

enum ECAM_Command
{
	CAM_COMMAND_NONE = 0,
	CAM_COMMAND_TOTHIRDPERSON = 1,
	CAM_COMMAND_TOFIRSTPERSON = 2
};

cvar_t *cam_command;
cvar_t *cam_snapto;
cvar_t *cam_idealyaw;
cvar_t *cam_idealpitch;
cvar_t *cam_idealdist;
cvar_t *cam_contain;

cvar_t *c_maxpitch;
cvar_t *c_minpitch;
cvar_t *c_maxyaw;
cvar_t *c_minyaw;
cvar_t *c_maxdistance;
cvar_t *c_mindistance;

vec3_t cam_ofs;

int cam_thirdperson;
int cam_mousemove;
int iMouseInUse = 0;
int cam_distancemove;
extern int mouse_x, mouse_y;
int cam_old_mouse_x, cam_old_mouse_y;
POINT cam_mouse;

static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright;
static kbutton_t cam_in, cam_out, cam_move;

void CAM_ToThirdPerson(void);
void CAM_ToFirstPerson(void);
void CAM_StartDistance(void);
void CAM_EndDistance(void);

float MoveToward(float cur, float goal, float maxspeed)
{
	if (cur != goal)
	{
		if (fabs(cur - goal) > 180.0)
		{
			if (cur < goal)
				cur += 360.0;
			else
				cur -= 360.0;
		}

		if (cur < goal)
		{
			if (cur < goal - 1.0)
				cur += (goal - cur) / 4.0;
			else
				cur = goal;
		}
		else
		{
			if (cur > goal + 1.0)
				cur -= (cur - goal) / 4.0;
			else
				cur = goal;
		}
	}

	if (cur < 0)
		cur += 360.0;
	else if (cur >= 360)
		cur -= 360;

	return cur;
}

typedef struct
{
	vec3_t boxmins, boxmaxs;
	float *mins, *maxs;
	vec3_t mins2, maxs2;
	float *start, *end;
	trace_t trace;
	int type;
	edict_t *passedict;
	qboolean monsterclip;
}
moveclip_t;

extern trace_t SV_ClipMoveToEntity(edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);

void CAM_Think(void)
{
	vec3_t origin;
	vec3_t ext, pnt, camForward, camRight, camUp;
	moveclip_t	clip;
	float dist;
	vec3_t camAngles;
	float flSensitivity;
#ifdef LATER
	int i;
#endif
	vec3_t viewangles;

	switch ((int)cam_command->value)
	{
		case CAM_COMMAND_TOTHIRDPERSON:
		{
			CAM_ToThirdPerson();
			break;
		}

		case CAM_COMMAND_TOFIRSTPERSON:
		{
			CAM_ToFirstPerson();
			break;
		}

		case CAM_COMMAND_NONE:
		default:
		{
			break;
		}
	}

	if (!cam_thirdperson)
		return;

#ifdef LATER
	if (cam_contain->value)
	{
		gEngfuncs.GetClientOrigin(origin);
		ext[0] = ext[1] = ext[2] = 0.0;
	}
#endif

	camAngles[PITCH] = cam_idealpitch->value;
	camAngles[YAW] = cam_idealyaw->value;
	dist = cam_idealdist->value;

	if (cam_mousemove)
	{
		GetCursorPos(&cam_mouse);

		if (!cam_distancemove)
		{
			if (cam_mouse.x > gEngfuncs.GetWindowCenterX())
			{
				if (camAngles[YAW] < c_maxyaw->value)
					camAngles[YAW] += (CAM_ANGLE_MOVE) * ((cam_mouse.x - gEngfuncs.GetWindowCenterX()) / 2);

				if (camAngles[YAW] > c_maxyaw->value)
					camAngles[YAW] = c_maxyaw->value;
			}
			else if (cam_mouse.x < gEngfuncs.GetWindowCenterX())
			{
				if (camAngles[YAW] > c_minyaw->value)
					camAngles[YAW] -= (CAM_ANGLE_MOVE) * ((gEngfuncs.GetWindowCenterX() - cam_mouse.x) / 2);

				if (camAngles[YAW] < c_minyaw->value)
					camAngles[YAW] = c_minyaw->value;
			}

			if (cam_mouse.y > gEngfuncs.GetWindowCenterY())
			{
				if (camAngles[PITCH] < c_maxpitch->value)
					camAngles[PITCH] += (CAM_ANGLE_MOVE) * ((cam_mouse.y - gEngfuncs.GetWindowCenterY()) / 2);

				if (camAngles[PITCH] > c_maxpitch->value)
					camAngles[PITCH] = c_maxpitch->value;
			}
			else if (cam_mouse.y < gEngfuncs.GetWindowCenterY())
			{
				if (camAngles[PITCH] > c_minpitch->value)
					camAngles[PITCH] -= (CAM_ANGLE_MOVE) * ((gEngfuncs.GetWindowCenterY() - cam_mouse.y) / 2);

				if (camAngles[PITCH] < c_minpitch->value)
					camAngles[PITCH] = c_minpitch->value;
			}

			if ((flSensitivity = gHUD.GetSensitivity()) != 0)
			{
				cam_old_mouse_x = cam_mouse.x * flSensitivity;
				cam_old_mouse_y = cam_mouse.y * flSensitivity;
			}
			else
			{
				cam_old_mouse_x = cam_mouse.x;
				cam_old_mouse_y = cam_mouse.y;
			}

			SetCursorPos(gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY());
		}
	}

	if (CL_KeyState(&cam_pitchup))
		camAngles[PITCH] += CAM_ANGLE_DELTA;
	else if (CL_KeyState(&cam_pitchdown))
		camAngles[PITCH] -= CAM_ANGLE_DELTA;

	if (CL_KeyState(&cam_yawleft))
		camAngles[YAW] -= CAM_ANGLE_DELTA;
	else if (CL_KeyState(&cam_yawright))
		camAngles[YAW] += CAM_ANGLE_DELTA;

	if (CL_KeyState(&cam_in))
	{
		dist -= CAM_DIST_DELTA;

		if (dist < CAM_MIN_DIST)
		{
			camAngles[PITCH] = 0;
			camAngles[YAW] = 0;
			dist = CAM_MIN_DIST;
		}
	}
	else if (CL_KeyState(&cam_out))
		dist += CAM_DIST_DELTA;

	if (cam_distancemove)
	{
		if (cam_mouse.y > gEngfuncs.GetWindowCenterY())
		{
			if (dist < c_maxdistance->value)
				dist += CAM_DIST_DELTA * ((cam_mouse.y - gEngfuncs.GetWindowCenterY()) / 2);

			if (dist > c_maxdistance->value)
				dist = c_maxdistance->value;
		}
		else if (cam_mouse.y < gEngfuncs.GetWindowCenterY())
		{
			if (dist > c_mindistance->value)
				dist -= (CAM_DIST_DELTA) * ((gEngfuncs.GetWindowCenterY() - cam_mouse.y) / 2);

			if (dist < c_mindistance->value)
				dist = c_mindistance->value;
		}

		cam_old_mouse_x = cam_mouse.x * gHUD.GetSensitivity();
		cam_old_mouse_y = cam_mouse.y * gHUD.GetSensitivity();
		SetCursorPos(gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY());
	}
#ifdef LATER
	if (cam_contain->value)
	{
		VectorCopy(origin, pnt);
		AngleVectors(camAngles, camForward, camRight, camUp);

		for (i = 0; i < 3 ; i++)
			pnt[i] += -dist*camForward[i];

		memset(&clip, 0, sizeof(moveclip_t));
		clip.trace = SV_ClipMoveToEntity(sv.edicts, r_refdef.vieworg, ext, ext, pnt);

		if (clip.trace.fraction == 1.0)
		{
			cam_idealpitch->value = camAngles[PITCH];
			cam_idealyaw->value = camAngles[YAW];
			cam_idealdist->value = dist;
		}
	}
	else
#endif
	{

		cam_idealpitch->value = camAngles[PITCH];
		cam_idealyaw->value = camAngles[YAW];
		cam_idealdist->value = dist;
	}

	VectorCopy(cam_ofs, camAngles);

	gEngfuncs.GetViewAngles((float *)viewangles);

	if (cam_snapto->value)
	{
		camAngles[YAW] = cam_idealyaw->value + viewangles[YAW];
		camAngles[PITCH] = cam_idealpitch->value + viewangles[PITCH];
		camAngles[2] = cam_idealdist->value;
	}
	else
	{
		if (camAngles[YAW] - viewangles[YAW] != cam_idealyaw->value)
			camAngles[YAW] = MoveToward(camAngles[YAW], cam_idealyaw->value + viewangles[YAW], CAM_ANGLE_SPEED);

		if (camAngles[PITCH] - viewangles[PITCH] != cam_idealpitch->value)
			camAngles[PITCH] = MoveToward(camAngles[PITCH], cam_idealpitch->value + viewangles[PITCH], CAM_ANGLE_SPEED);

		if (fabs(camAngles[2] - cam_idealdist->value) < 2.0)
			camAngles[2] = cam_idealdist->value;
		else
			camAngles[2] += (cam_idealdist->value - camAngles[2]) / 4.0;
	}
#ifdef LATER
	if (cam_contain->value)
	{
		dist = camAngles[ROLL];
		camAngles[ROLL] = 0;

		VectorCopy(origin, pnt);
		AngleVectors(camAngles, camForward, camRight, camUp);

		for (i = 0; i < 3; i++)
			pnt[i] += -dist * camForward[i];

		memset(&clip, 0, sizeof(moveclip_t));
		ext[0] = ext[1] = ext[2] = 0.0;
		clip.trace = SV_ClipMoveToEntity(sv.edicts, r_refdef.vieworg, ext, ext, pnt);

		if (clip.trace.fraction != 1.0)
			return;
	}
#endif
	cam_ofs[0] = camAngles[0];
	cam_ofs[1] = camAngles[1];
	cam_ofs[2] = dist;
}

extern void KeyDown(kbutton_t *b);
extern void KeyUp(kbutton_t *b);

void CAM_PitchUpDown(void) { KeyDown(&cam_pitchup); }
void CAM_PitchUpUp(void) { KeyUp(&cam_pitchup); }
void CAM_PitchDownDown(void) { KeyDown(&cam_pitchdown); }
void CAM_PitchDownUp(void) { KeyUp(&cam_pitchdown); }
void CAM_YawLeftDown(void) { KeyDown(&cam_yawleft); }
void CAM_YawLeftUp(void) { KeyUp(&cam_yawleft); }
void CAM_YawRightDown(void) { KeyDown(&cam_yawright); }
void CAM_YawRightUp(void) { KeyUp(&cam_yawright); }
void CAM_InDown(void) { KeyDown(&cam_in); }
void CAM_InUp(void) { KeyUp(&cam_in); }
void CAM_OutDown(void) { KeyDown(&cam_out); }
void CAM_OutUp(void) { KeyUp(&cam_out); }

void CAM_ToThirdPerson(void)
{
	vec3_t viewangles;

#if !defined (_DEBUG)
	if (gEngfuncs.GetMaxClients() > 1)
		return;
#endif

	gEngfuncs.GetViewAngles((float *)viewangles);

	if (!cam_thirdperson)
	{
		cam_thirdperson = 1; 

		cam_ofs[YAW] = viewangles[YAW];
		cam_ofs[PITCH] = viewangles[PITCH];
		cam_ofs[2] = CAM_MIN_DIST;
	}

	gEngfuncs.Cvar_SetValue("cam_command", 0);
}

void CAM_ToFirstPerson(void)
{
	cam_thirdperson = 0;

	gEngfuncs.Cvar_SetValue("cam_command", 0);
}

void CAM_ToggleSnapto(void)
{
	cam_snapto->value = !cam_snapto->value;
}

void CAM_Init(void)
{
	Cmd_HookCmd("+campitchup", CAM_PitchUpDown);
	Cmd_HookCmd("-campitchup", CAM_PitchUpUp);
	Cmd_HookCmd("+campitchdown", CAM_PitchDownDown);
	Cmd_HookCmd("-campitchdown", CAM_PitchDownUp);
	Cmd_HookCmd("+camyawleft", CAM_YawLeftDown);
	Cmd_HookCmd("-camyawleft", CAM_YawLeftUp);
	Cmd_HookCmd("+camyawright", CAM_YawRightDown);
	Cmd_HookCmd("-camyawright", CAM_YawRightUp);
	Cmd_HookCmd("+camin", CAM_InDown);
	Cmd_HookCmd("-camin", CAM_InUp);
	Cmd_HookCmd("+camout", CAM_OutDown);
	Cmd_HookCmd("-camout", CAM_OutUp);
	Cmd_HookCmd("thirdperson", CAM_ToThirdPerson);
	Cmd_HookCmd("firstperson", CAM_ToFirstPerson);
	Cmd_HookCmd("+cammousemove",CAM_StartMouseMove);
	Cmd_HookCmd("-cammousemove",CAM_EndMouseMove);
	Cmd_HookCmd("+camdistance", CAM_StartDistance);
	Cmd_HookCmd("-camdistance", CAM_EndDistance);
	Cmd_HookCmd("snapto", CAM_ToggleSnapto);

	cam_command = gEngfuncs.pfnGetCvarPointer("cam_command");
	cam_snapto = gEngfuncs.pfnGetCvarPointer("cam_snapto");
	cam_idealyaw = gEngfuncs.pfnGetCvarPointer("cam_idealyaw");
	cam_idealpitch = gEngfuncs.pfnGetCvarPointer("cam_idealpitch");
	cam_idealdist = gEngfuncs.pfnGetCvarPointer("cam_idealdist");
	cam_contain = gEngfuncs.pfnGetCvarPointer("cam_contain");

	c_maxpitch = gEngfuncs.pfnGetCvarPointer("c_maxpitch");
	c_minpitch = gEngfuncs.pfnGetCvarPointer("c_minpitch");
	c_maxyaw = gEngfuncs.pfnGetCvarPointer("c_maxyaw");
	c_minyaw = gEngfuncs.pfnGetCvarPointer("c_minyaw");
	c_maxdistance = gEngfuncs.pfnGetCvarPointer("c_maxdistance");
	c_mindistance = gEngfuncs.pfnGetCvarPointer("c_mindistance");
}

void CAM_ClearStates(void)
{
	vec3_t viewangles;

	gEngfuncs.GetViewAngles((float *)viewangles);

	cam_pitchup.state = 0;
	cam_pitchdown.state = 0;
	cam_yawleft.state = 0;
	cam_yawright.state = 0;
	cam_in.state = 0;
	cam_out.state = 0;

	cam_thirdperson = 0;
	cam_command->value = 0;
	cam_mousemove = 0;

	cam_snapto->value = 0;
	cam_distancemove = 0;

	cam_ofs[0] = 0.0;
	cam_ofs[1] = 0.0;
	cam_ofs[2] = CAM_MIN_DIST;

	cam_idealpitch->value = viewangles[PITCH];
	cam_idealyaw->value = viewangles[YAW];
	cam_idealdist->value = CAM_MIN_DIST;
}

void CAM_StartMouseMove(void)
{
	float flSensitivity;

	if (cam_thirdperson)
	{
		if (!cam_mousemove)
		{
			cam_mousemove = 1;
			iMouseInUse = 1;
			GetCursorPos(&cam_mouse);

			if ((flSensitivity = gHUD.GetSensitivity()) != 0)
			{
				cam_old_mouse_x = cam_mouse.x * flSensitivity;
				cam_old_mouse_y = cam_mouse.y * flSensitivity;
			}
			else
			{
				cam_old_mouse_x = cam_mouse.x;
				cam_old_mouse_y = cam_mouse.y;
			}
		}
	}
	else
	{
		cam_mousemove = 0;
		iMouseInUse = 0;
	}
}

void CAM_EndMouseMove(void)
{
	cam_mousemove = 0;
	iMouseInUse = 0;
}

void CAM_StartDistance(void)
{
	if (cam_thirdperson)
	{
		if (!cam_distancemove)
		{
			cam_distancemove = 1;
			cam_mousemove = 1;
			iMouseInUse = 1;
			GetCursorPos(&cam_mouse);
			cam_old_mouse_x = cam_mouse.x * gHUD.GetSensitivity();
			cam_old_mouse_y = cam_mouse.y * gHUD.GetSensitivity();
		}
	}
	else
	{
		cam_distancemove = 0;
		cam_mousemove = 0;
		iMouseInUse = 0;
	}
}

void CAM_EndDistance(void)
{
	cam_distancemove = 0;
	cam_mousemove = 0;
	iMouseInUse = 0;
}

int CL_IsThirdPerson(void)
{
	return (cam_thirdperson ? 1 : 0) || (g_iUser1 && (g_iUser2 == gEngfuncs.GetLocalPlayer()->index));
}

void CL_CameraOffset(float *ofs)
{
	VectorCopy(cam_ofs, ofs);
}